Skip to content

Support OpenAPI 3.1: resolve array-valued type (unions & nullable) (#375) - #376

Merged
jemacineiras merged 4 commits into
sngular:mainfrom
joseegman-idoneea:feat/375-openapi-3.1-support
Jul 23, 2026
Merged

Support OpenAPI 3.1: resolve array-valued type (unions & nullable) (#375)#376
jemacineiras merged 4 commits into
sngular:mainfrom
joseegman-idoneea:feat/375-openapi-3.1-support

Conversation

@joseegman-idoneea

Copy link
Copy Markdown
Contributor

What

First step of #375 (Support OpenAPI 3.1.x): make the load-bearing 3.1 schema construct —
an array-valued type — generate correctly.

Root cause

OpenAPI 3.1 / JSON Schema 2020-12 lets a schema's type be an array, e.g.
type: ["string", "null"] (the nullable idiom that replaces 3.0's nullable: true, plus general
unions). ApiTool.getType() read the type via JsonNode.textValue(), which returns null for an
array node and collapsed to "". As a result every isObject/isArray/isString/isNumber/isDateTime
predicate and MapperUtil.getSimpleType failed, and the generator silently produced wrong/empty
Java types for those properties. It reproduces on any 3.1 spec using array types.

Change

  • ApiTool.getType — when type is an array, resolve to the first non-"null" entry
    ("null" only marks nullability). Every isX predicate and getSimpleType then work unchanged
    for union/nullable types. Scalar type handling is untouched.
  • MapperUtil.processNumber — read the type via ApiTool.getType instead of
    schema.get("type").asText(), so array-valued numeric types resolve.
  • Add TypeConstants.NULL.

Tests

New 3.1.0 fixture testOpenApi31Types with golden assets, exercising:

property schema type generated
nickname ["string", "null"] String
age ["integer", "null"] Integer
score ["number", "null"] + format: double Double
loginCount integer + format: int64 Long
tags array of string List<String>

OpenApiGeneratorTest: Tests run: 45, Failures: 0, Errors: 0; checkstyle:check BUILD SUCCESS.
Before the fix the array-typed properties collapsed to no/incorrect type.

Versioning

Bumps 6.3.26.4.0 (minor, new feature) across multiapi-engine,
scs-multiapi-maven-plugin (own version + engine dependency) and scs-multiapi-gradle-plugin.

Scope / follow-up

Focused on the one construct that makes 3.1 schemas actually generate. Remaining 3.1 checklist
items — JSON Schema examples array, $ref with sibling keywords, top-level webhooks — are
lower impact and tracked in #375 for follow-up PRs. Existing 3.0 specs are unaffected (non-array
type path is unchanged).

Closes the first checklist items of #375.

🤖 Generated with Claude Code

…able)

OpenAPI 3.1 / JSON Schema 2020-12 allows a schema's `type` to be an array,
e.g. type: ["string", "null"] (the nullable idiom that replaces 3.0's
`nullable: true`, and general unions). ApiTool.getType() read the type via
JsonNode.textValue(), which returns null for an array node and collapsed to
"", so every isObject/isArray/isString/isNumber/isDateTime predicate and
MapperUtil.getSimpleType failed -> the generator silently produced wrong or
empty types for those fields.

- ApiTool.getType: when `type` is an array, resolve to the first non-"null"
  entry ("null" only marks the type as nullable). All the isX predicates and
  getSimpleType then work unchanged for union/nullable types.
- MapperUtil.processNumber: read the type via ApiTool.getType instead of
  schema.get("type").asText(), so array-valued numeric types resolve.
- Add TypeConstants.NULL.
- Add a 3.1.0 regression fixture (testOpenApi31Types) covering ["string",
  "null"], ["integer","null"], ["number","null"]+double, int64 and a plain
  array, with golden assets.

Bumps version 6.3.2 -> 6.4.0 across the engine, maven and gradle modules.
Non-array `type` handling is unchanged, so existing 3.0 specs are unaffected.

Follow-up 3.1 items (examples array, $ref siblings, webhooks) tracked in sngular#375.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codacy-production

codacy-production Bot commented Jul 22, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 2 minor

Alerts:
⚠ 2 issues (≤ 0 issues of at least minor severity)

Results:
2 new issues

Category Results
CodeStyle 2 minor

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@jemacineiras
jemacineiras self-requested a review July 22, 2026 16:33
@jemacineiras jemacineiras self-assigned this Jul 22, 2026
@jemacineiras jemacineiras added enhancement New feature or request release Adding this tag to a PR will cause a release on merge labels Jul 22, 2026
jemacineiras
jemacineiras previously approved these changes Jul 22, 2026
jemacineiras
jemacineiras previously approved these changes Jul 22, 2026
Gradle 9.x plugin validation fails the build when a task type is neither
@CacheableTask nor @DisableCachingByDefault. OpenApiTask and AsyncApiTask
are code generators whose spec-file inputs are not declared as cacheable
inputs, so caching is disabled explicitly with a documented reason.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jemacineiras
jemacineiras merged commit a549c5f into sngular:main Jul 23, 2026
6 of 7 checks passed
jemacineiras pushed a commit that referenced this pull request Jul 23, 2026
* Fix #375: support OpenAPI 3.1 array-valued type (unions & nullable)

OpenAPI 3.1 / JSON Schema 2020-12 allows a schema's `type` to be an array,
e.g. type: ["string", "null"] (the nullable idiom that replaces 3.0's
`nullable: true`, and general unions). ApiTool.getType() read the type via
JsonNode.textValue(), which returns null for an array node and collapsed to
"", so every isObject/isArray/isString/isNumber/isDateTime predicate and
MapperUtil.getSimpleType failed -> the generator silently produced wrong or
empty types for those fields.

- ApiTool.getType: when `type` is an array, resolve to the first non-"null"
  entry ("null" only marks the type as nullable). All the isX predicates and
  getSimpleType then work unchanged for union/nullable types.
- MapperUtil.processNumber: read the type via ApiTool.getType instead of
  schema.get("type").asText(), so array-valued numeric types resolve.
- Add TypeConstants.NULL.
- Add a 3.1.0 regression fixture (testOpenApi31Types) covering ["string",
  "null"], ["integer","null"], ["number","null"]+double, int64 and a plain
  array, with golden assets.

Bumps version 6.3.2 -> 6.4.0 across the engine, maven and gradle modules.
Non-array `type` handling is unchanged, so existing 3.0 specs are unaffected.

Follow-up 3.1 items (examples array, $ref siblings, webhooks) tracked in #375.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Support OpenAPI 3.1 top-level webhooks (#375)

OpenAPI 3.1 adds a top-level `webhooks` object: a map of named Path Item
Objects describing out-of-band requests. The generator only iterated
`paths`, so webhooks were ignored - no handler interface, no payload models.

Add OpenApiUtil.mergeWebhooksIntoPaths, invoked right after parsing in
OpenApiGenerator.processFile. Each webhook (keyed by name) is merged into
`paths` under a "/"-prefixed key so the existing pipeline generates a
handler interface for its operations and the request/response payload
models. `paths` is created if the contract has none, and existing `paths`
entries are never overwritten.

Adds a 3.1.0 webhooks-only regression fixture (testWebhooks) with golden
assets (NewPetApi + PetDTO).

Stacked on the 6.4.0 array-type work (#376); both are part of the 3.1
support milestone, so the version stays 6.4.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Default tags on tag-less webhook operations to avoid NPE

Webhook operations normally omit `tags`, but createOperation requires
one (Objects.requireNonNull), so a realistic tag-less webhook crashed
with a NullPointerException in the default by-url grouping mode - and
was silently skipped in tag-grouping mode. The prior test masked this
by giving the webhook explicit tags.

mergeWebhooksIntoPaths now defaults each webhook operation's tags to the
webhook name when absent/empty, and skips blank keys (guards the
pathUrl.split()[1] grouping). The testWebhooks fixture is updated to the
realistic tag-less shape to cover this.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Harden 3.1 type/webhook handling and add edge-case tests

getType: extract array-type resolution into a helper. A genuine multi-type
union (e.g. ["string","integer"]) now logs a warning and uses the first
concrete type; a "null"-only/empty type array falls back to `object`
instead of an empty type string.

Adds edge-case regression fixtures:
- testOpenApi31Union: union -> String (+warning), ["null"] -> Object
- testWebhookPathCollision: webhook name colliding with an existing path
  is dropped (existing paths take precedence).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Annotate Gradle tasks with @DisableCachingByDefault

Gradle 9.x plugin validation fails the build when a task type is neither
@CacheableTask nor @DisableCachingByDefault. OpenApiTask and AsyncApiTask
are code generators whose spec-file inputs are not declared as cacheable
inputs, so caching is disabled explicitly with a documented reason.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: update thollander/actions-comment-pull-request to v2 (Node 20 deprecated)

---------

Co-authored-by: joseegarcia <jose.garcia@disashop.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request release Adding this tag to a PR will cause a release on merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants